/*
 *   Copyright 2012, Thomas Kerber
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
package milk.jpatch.code;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.bcel.classfile.Attribute;
import org.apache.bcel.classfile.Code;

import milk.jpatch.CPoolMap;
import milk.jpatch.Util;
import milk.jpatch.attribs.AttributePatch;
import milk.jpatch.attribs.AttributesPatch;

/**
 * A patch of a "Code" attribute.
 * @author Thomas Kerber
 * @version 1.0.0
 */
public abstract class CodePatch extends AttributePatch{
    
    private static final long serialVersionUID = 2649429120448088550L;
    
    /**
     * Hooks for the old code points.
     */
    protected List<CodePointHook> oldCPs = new ArrayList<CodePointHook>();
    /**
     * Hooks for the new code points.
     */
    protected List<CodePointHook> newCPs = new ArrayList<CodePointHook>();
    /**
     * The component attributes patch.
     */
    protected AttributesPatch attributesPatch;
    
    /**
     * Adds to the old code point hooks.
     * @param cp The hook to add.
     */
    public void addOldCP(CodePointHook cp){
        oldCPs.add(cp);
    }
    
    /**
     * Adds to the new code point hooks.
     * @param cp The hook to add.
     */
    public void addNewCP(CodePointHook cp){
        newCPs.add(cp);
    }
    
    /**
     * Creates
     * @param attributesPatch The component attribute patch.
     */
    protected CodePatch(AttributesPatch attributesPatch){
        this.attributesPatch = attributesPatch;
    }
    
    /**
     * Generates.
     * @param old The old attributes.
     * @param new_ The new attributes.
     * @param patches The already applied attribute patches.
     * @param map The cpool map.
     */
    public static void generate(Attribute[] old, Attribute[] new_,
            List<AttributePatch> patches, CPoolMap map){
        Code oldCode = null;
        for(Attribute a : old){
            if(a instanceof Code){
                oldCode = (Code)a;
                break;
            }
        }
        Code newCode = null;
        for(Attribute a : new_){
            if(a instanceof Code){
                newCode = (Code)a;
                break;
            }
        }
        if(newCode == null)
            return;
        patches.add(generate(oldCode, newCode, map));
    }
    
    /**
     * Generates.
     * @param old The old code.
     * @param new_ The new code.
     * @param map The cpool map.
     * @return The patch.
     */
    public static CodePatch generate(Code old, Code new_, CPoolMap map){
        // AttributesPatch
        AttributesPatch attribPatch = AttributesPatch.generate(
                old == null ? new Attribute[0] : old.getAttributes(),
                new_.getAttributes(), map);
        
        // TODO: better code patches.
        if(old == null)
            return new CodeReplace(attribPatch, new_);
        if(Util.equals(old, new_, map))
            return new CodeID(attribPatch);
        return new CodeReplace(attribPatch, new_);
    }
    
    @Override
    public List<Attribute> patch(List<Attribute> attribs, CPoolMap map){
        for(int i = 0; i < attribs.size(); i++){
            if(attribs.get(i) instanceof Code){
                Code newCode = this.patch((Code)attribs.get(i), map);
                // Aaand apply attribs patch...
                List<Attribute> codeAttribs = new ArrayList<Attribute>(
                        Arrays.asList(newCode.getAttributes()));
                codeAttribs = attributesPatch.patch(codeAttribs, map);
                newCode.setAttributes(codeAttribs.toArray(
                        new Attribute[codeAttribs.size()]));
                // Aaand add and return.
                attribs.set(i, newCode);
                return attribs;
            }
        }
        throw new IllegalStateException("Cannot patch nonexistant code!");
    }
    
    public abstract Code patch(Code c, CPoolMap map);
    
}
